package org.yar.activities;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.yar.R;
import org.yar.common.EncodingUtils;
import org.yar.common.Preferences;
import org.yar.reader.TextChunk;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ViewSwitcher;

public class ReaderActivity 
	extends Activity 
	implements AbsListView.OnScrollListener, ViewSwitcher.ViewFactory {
	
	static class UpdateTextSwitcherTask implements Runnable {
		private final int [] displayStates = { 
//				DISPLAY_NAME,
				DISPLAY_PATH,
				DISPLAY_LAST_MOD_DATE 
				};
		private int dispIndex = 0;
		private int duration;
		private ReaderActivity activity;
		
		
		public UpdateTextSwitcherTask(ReaderActivity activity, int duration) //in seconds
		{
			this.duration = duration;
			this.activity = activity;
		}
		
		@Override
		public void run() {
			
			if (dispIndex >= displayStates.length)
			{
				dispIndex = 0;
			}
			int toDisplay = displayStates[dispIndex++];
			Message message = new Message();
			message.what = toDisplay;
			activity.handler.sendMessage(message);
			
			activity.handler.postDelayed(this, duration*1000);
		}
		
	}
	
	static class ViewHolder {
		TextView text;
	}
	
	static class LoadFileTask extends AsyncTask<File, TextChunk, Long>
	{
		static final int MAX_BYTES_PER_TEXTVIEW = 2048;
		
		private ReaderActivity activity;
		private String encoding;
		
		public LoadFileTask(ReaderActivity activity, String encoding)
		{
			this.activity = activity;
			this.encoding = encoding;
		}
		
		/**
		 * Gets the index of the last occurring newline ('\n') 
		 * within the given array arr.
		 * @param arr
		 * @return
		 */
		private int getLastNewline(byte [] arr)
		{
			for (int i = arr.length-1; i >= 0; i--)
			{
				if (arr[i] == (byte)10)
				{
					return i;
				}
			}
			return -1;
		}

		@Override
		protected Long doInBackground(File... params) {
			File file = params[0];
			
			try {
				RandomAccessFile raf = new RandomAccessFile(file, "r");

				int maxBytesPerView = MAX_BYTES_PER_TEXTVIEW;
				
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				byte buf[] = new byte[maxBytesPerView];
				long len, currCycleBytesRead = 0, totalBytesRead = 0;
				while ((len = raf.read(buf)) != -1) {
					
					baos.write(buf, 0, (int)len);
					
					int newline = getLastNewline(buf);
					if (newline != -1 && currCycleBytesRead + len >= maxBytesPerView)
					{
						/* emit a new string chunk */
						byte [] chunk = baos.toByteArray();
						String s = new String(chunk, 0, (int)(currCycleBytesRead+newline), encoding);
						publishProgress(new TextChunk(totalBytesRead, s));
						
						baos.reset();
						
						/* reset the file pointer to the position where the last newline was found */
						raf.seek(totalBytesRead+newline+1);
						
						/* reset the bytes read counter */
						currCycleBytesRead = 0;
						
						/* increment the total bytes read counter */
						totalBytesRead += (newline+1);
					}
					else
					{	
						if (totalBytesRead+len == raf.length())
						{
							/* publish the last segment of the file */
							byte [] chunk = baos.toByteArray();
							String s = new String (chunk, 0, (int)(currCycleBytesRead+len), encoding);
							publishProgress(new TextChunk(totalBytesRead, s));
						}
						
						currCycleBytesRead += len;
						totalBytesRead += len;
					}
					
				}

				raf.close();
				
				return totalBytesRead;
			} catch (IOException ex)
			{
				Log.e(TAG, String.format("%s", ex.toString()));
				return (long)-1;
			}
			
		}

		@Override
		protected void onPostExecute(Long result) {
			activity.fileLength = result;
			
			if (activity.pdLoading.isShowing())
			{
				activity.pdLoading.dismiss();
				
				for (int i = 0; i < activity.listAdapter.getCount(); ++i)
				{
					TextChunk chunk = activity.listAdapter.getItem(i);
					if (chunk.getStartByte() >= activity.currBytePos)
					{
						activity.lvText.setSelection(i);
						break;
					}
				}
				
			}
			
			if (activity.updateTextSwitcherTask != null)
			{
				activity.handler.removeCallbacks(activity.updateTextSwitcherTask);
			}
			activity.updateTextSwitcherTask = new UpdateTextSwitcherTask(activity, 3);
			activity.handler.postDelayed(activity.updateTextSwitcherTask, 1000);
			
			
		}

		@Override
		protected void onProgressUpdate(TextChunk... values) {
			/* push the newly read file chunk into our text list */
			for (TextChunk s: values)
			{
				activity.listAdapter.add(s);
			}
			
		}
		
	}
	
	static final int READER_CMD_OPEN_FILE = 0;
	static final int READER_DG_LOADING = 0; 
	static final String READER_KEY_FILENAME = "file";
	static final String TAG = "YetAnotherReader.Reader";
	
	static final int DISPLAY_NAME = 1;
	static final int DISPLAY_LAST_MOD_DATE = 2;
	static final int DISPLAY_PATH = 3;
	
	private Date lastReadTime;
	private File currFile;
	private String currEncoding;
	private ArrayAdapter<TextChunk> listAdapter;
	private LoadFileTask loadFileTask;
	private long currBytePos;
	private long fileLength;
	private Handler handler;
	private UpdateTextSwitcherTask updateTextSwitcherTask;
	private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	private LayoutInflater inflater;
	
	private Preferences preferences;
	private SharedPreferences sharedPrefs;
	
	private ProgressDialog pdLoading;
	private ListView lvText;
//	private ImageButton ibPageUp;
//	private ImageButton ibPageDown;
	private TextSwitcher tsReaderTop;
	private Toast toast;
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.reader);
        setTitle(R.string.app_name);
        
        inflater = LayoutInflater.from(this);
        
        handler = new Handler() {
        	@Override
			public void handleMessage(Message msg)
        	{
        		final Message message = msg;
        		runOnUiThread(new Runnable() {
					@Override
					public void run() {
						
						switch (message.what)
		        		{
						/* unused */
		        		case DISPLAY_NAME: {
		        			tsReaderTop.setText("File name: " + currFile.getName());
		        			break;
		        		}
		        		case DISPLAY_LAST_MOD_DATE: {
		        			tsReaderTop.setText("Last modified time: " + dateFormat.format(new Date(currFile.lastModified())));
		        			break;
		        		}
		        		case DISPLAY_PATH: {
		        			try {
		        				tsReaderTop.setText("Current file: " + currFile.getCanonicalPath());
		        			} catch (IOException ie) { }
		        			break;
		        		}
		        		default: {
		        			Log.v(TAG, "unknown message type: " + message.what);
		        			break;
		        		}
		        		}
					}
        			
        		});
        	}
        };
        
//        ibPageUp = (ImageButton)findViewById(R.id.ibPageUp);
//        ibPageDown = (ImageButton)findViewById(R.id.ibPageDown);
//        
//        ibPageUp.setOnClickListener(new View.OnClickListener() {
//			
//			@Override
//			public void onClick(View v) {
//				int pos = lvText.getFirstVisiblePosition();
//				if (pos == 0)
//				{
//					return;
//				}
//				lvText.setSelection(pos-1);
//			}
//		});
//        
//        ibPageDown.setOnClickListener(new View.OnClickListener() {
//			
//			@Override
//			public void onClick(View v) {
//				int pos = lvText.getFirstVisiblePosition();
//				if (pos == listAdapter.getCount()-1)
//				{
//					return;
//				}
//				lvText.setSelection(pos+1);
//			}
//		});
        
        lvText = (ListView)findViewById(R.id.lvText);
        listAdapter = new ArrayAdapter<TextChunk>(this, R.layout.text_item)
		{
			/* We do not want the user to be able to select any
			 * items from the 'list', as that would not make sense in this
			 * context */
        	@Override
			public boolean areAllItemsEnabled() 
	        { 
	                return false; 
	        }
        	
        	@Override
	        public boolean isEnabled(int position) 
	        { 
	                return false; 
	        }
        	
        	@Override
    		public View getView(int position, View convertView, ViewGroup parent) {
        		if (convertView == null)
        		{
        			convertView = inflater.inflate(R.layout.text_item, null);
        		}
        		TextChunk tc = getItem(position);
        		convertView.setBackgroundColor(sharedPrefs.getInt(YARPreferencesActivity.PREF_BG_COLOR, Color.BLACK));
        		((TextView)convertView).setText(tc.getText());
        		((TextView)convertView).setTextColor(sharedPrefs.getInt(YARPreferencesActivity.PREF_TEXT_COLOR, Color.WHITE));
        		((TextView)convertView).setTextSize(sharedPrefs.getInt(YARPreferencesActivity.PREF_TEXT_SIZE, 18));
        		return convertView;
        	}
		};
        lvText.setAdapter(listAdapter);
		lvText.setFastScrollEnabled(true);
		lvText.setOnScrollListener(this);
		
		tsReaderTop = (TextSwitcher)findViewById(R.id.tsReaderTop);
		tsReaderTop.setInAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_in));
		tsReaderTop.setOutAnimation(AnimationUtils.loadAnimation(this, android.R.anim.fade_out));
		tsReaderTop.setBackgroundDrawable(getResources().getDrawable(R.drawable.reader_info_gradient));
        tsReaderTop.setFactory(this);
        
        preferences = new Preferences(this);
		
		sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
		
		toast = Toast.makeText(this, "", Toast.LENGTH_LONG);
		toast.setGravity(Gravity.BOTTOM, 0, 5);
	}
	
	@Override
	public void onStart()
	{
		super.onStart();
		
		if (currFile != null)
		{
			return;
		}
		
		Intent intent = getIntent();
		if (intent == null)
		{
			return;
		}
		
		String filename = intent.getStringExtra(READER_KEY_FILENAME);
		if (filename == null)
		{
			return;
		}
		
		currFile = new File(filename);
	}
	
	@Override
	public void onResume()
	{
		super.onResume();
		
		try {
			currBytePos = preferences.getInt(
					currFile.getCanonicalPath(), 
					"BYTE_POS", 
					0);
			/*
			 * the encoding of the file is read in the following order:
			 * 1) in file specific preference
			 * 2) in general preference
			 * 3) default value
			 */
			currEncoding = preferences.getString(
					currFile.getCanonicalPath(), 
					"ENCODING", 
					sharedPrefs.getString(YARPreferencesActivity.PREF_DEF_ENCODING, "UTF-8"));
		} catch (IOException e) {
		}
		
		setRequestedOrientation(sharedPrefs.getInt("orientation", ActivityInfo.SCREEN_ORIENTATION_PORTRAIT));
		
		
		loadFile(currFile, currEncoding);
	}
	
	@Override
	public void onPause()
	{
		super.onPause();
		
		lastReadTime = new Date();
		
		try {
			int position = lvText.getFirstVisiblePosition();
			
			preferences.addFile(currFile);
			preferences.putLong(currFile.getCanonicalPath(), "BYTE_POS", listAdapter.getItem(position).getStartByte());
			preferences.putLong(currFile.getCanonicalPath(), "LENGTH", fileLength);
			preferences.putString(currFile.getCanonicalPath(), "ENCODING", currEncoding);
			
		} catch (IOException e) {
		}
		
		preferences.save();
		
		SharedPreferences.Editor editor = sharedPrefs.edit();
		editor.putInt("orientation", getRequestedOrientation());
		
		editor.commit();
	}
	
	private boolean loadFile(File f, String encoding)
	{
		if (!f.exists() || !f.isFile() || !f.canRead())
		{
			return false;
		}
		/* No need to reload the file if there have been no changes*/
//		if (lastReadTime != null && lastReadTime.after(new Date(f.lastModified())))
//		{
//			return false;
//		}
		
//		setTitle(formatFilename(f, 14));
		
		listAdapter.clear();
		
		showDialog(READER_DG_LOADING);
		
		loadFileTask = new LoadFileTask(this, encoding);
		loadFileTask.execute(f);
		
		return true;
	}
	
	/**
	 * Formats the name of the given file by shortening it
	 * if the filename is longer than maxChars.
	 * @param f
	 * @param maxChars
	 * @return
	 */
	private String formatFilename(File f, int maxChars)
	{
		String name = f.getName();
		if (name.length() < maxChars)
		{
			return name;
		}
		else
		{
			return name.substring(0, maxChars/2-1) + "..." + name.substring(maxChars/2+3, name.length());
		}
	}
	
	@Override
    protected Dialog onCreateDialog(int id) {
		
		switch (id) {
		case READER_DG_LOADING: {
			pdLoading = new ProgressDialog(this);
			pdLoading.setMessage("Loading file...");
			pdLoading.setIndeterminate(true);
			pdLoading.setCancelable(false);
			pdLoading.setProgressStyle(ProgressDialog.STYLE_SPINNER);
			return pdLoading;
		}
		}
		
		return null;
	}
	
	@Override
    public boolean onCreateOptionsMenu(Menu menu) {
		Log.v(TAG, "showing options menu");
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.reader_menu, menu);
        SubMenu submenu = menu.addSubMenu(Menu.NONE, Menu.NONE, Menu.NONE, "Encoding");
        submenu.setIcon(R.drawable.encoding);
        for (String enc: EncodingUtils.getEncodings())
        {
        	submenu.add(enc);
        }
        
        return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	
    	switch (item.getItemId()) {
    	case R.id.RMFileBrowser: {
    		openFileBrowser();
    		return true;
    	}
    	case R.id.RMPreferences: {
    		openPreferences();
    		return true;
    	}
    	case R.id.RMOrientation: {
    		if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
    		{
    			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    		}
    		else
    		{
    			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    		}
    		return true;
    	}
    	}
    	
    	if (EncodingUtils.isValidEncoding((String)item.getTitle()))
    	{
    		currEncoding = (String)item.getTitle();
    		loadFile(currFile, currEncoding);
    		return true;
    	}
    	
    	return super.onOptionsItemSelected(item);
    }
    
    private void openFileBrowser()
    {
    	Intent intent = new Intent();
		intent.setClass(ReaderActivity.this, FileBrowserActivity.class);
		
		startActivity(intent);
    }
    
    private void openPreferences()
    {
    	Intent intent = new Intent();
    	intent.setClass(ReaderActivity.this, YARPreferencesActivity.class);
    	
    	startActivity(intent);
    }

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		if (firstVisibleItem >= totalItemCount ||
				fileLength == 0)
		{
			/* the text list has not been constructed yet */
//			ibPageUp.setVisibility(View.INVISIBLE);
//			ibPageDown.setVisibility(View.INVISIBLE);
		}
		else
		{
//			ibPageUp.setVisibility(firstVisibleItem == 0 ? View.INVISIBLE : View.VISIBLE);
//			ibPageDown.setVisibility(firstVisibleItem+visibleItemCount >= totalItemCount ? View.INVISIBLE : View.VISIBLE);
			
			long startByte = listAdapter.getItem(firstVisibleItem).getStartByte();
			toast.setText(String.format("%d%%", startByte*100/fileLength));
		}
		
	}

	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		if (scrollState == OnScrollListener.SCROLL_STATE_FLING)
		{
			toast.show();
		}
	}

	@Override
	public View makeView() {
		TextView tv = new TextView(this);
		tv.setTextSize(16);
		tv.setGravity(Gravity.LEFT);
		tv.setTextColor(this.getResources().getColor(android.R.color.black));
		return tv;
	}

}
